home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Night Owl 6
/
Night Owl's Shareware - PDSI-006 - Night Owl Corp (1990).iso
/
016a
/
gofer221.zip
/
CH12
< prev
next >
Wrap
Text File
|
1991-11-20
|
20KB
|
529 lines
Introduction to Gofer 12. DIALOGUES: INPUT AND OUTPUT
12. DIALOGUES: INPUT AND OUTPUT
The Gofer system implements a subset of the facilities for programs
involving I/O described in the Haskell report [5]. In particular, this
makes it possible for Gofer programs to be run interactively, and to
make limited use of text files for both reading and writing. A
significant factor in the design of the Haskell I/O facilities is that
it allows the use of such programs without loss of referential
transparency.
12.1 Basic description
----------------------
Programs using the I/O facilities in Gofer are modelled by functions of
type Dialogue, defined by the type synonym:
type Dialogue = [Response] -> [Request]
In other words, a Gofer program produces a list of output values, each
of which may be thought of as a request for some particular input or
output action, and obtains the corresponding list of operating system
responses as its input. Note that the input list of responses will be
evaluated lazily; i.e. we can ensure that we do not attempt to obtain
the response to a given request until that request has been completed.
The current range of requests supported by Gofer is described by the
following datatype definition, taken from the standard prelude:
data Request = -- file system requests:
ReadFile String
| WriteFile String String
| AppendFile String String
-- channel system requests:
| ReadChan String
| AppendChan String String
-- environment requests:
| Echo Bool
Each response is an element of the type defined by the following
datatype definition, using an auxiliary datatype IOError to describe a
variety of error conditions that may occur:
data Response = Success
| Str String
| Failure IOError
data IOError = WriteError String
| ReadError String
| SearchError String
| FormatError String
| OtherError String
The following list describes the kind of I/O behaviour specified by
each form of Request and indicates the possible Response values that
may be obtained in each case:
o ReadFile string: Read contents of file named by "string".
49
Introduction to Gofer 12.1 Basic description
Possible responses to this request are:
o Str contents if the request is successful, where "contents" is
a string (evaluated lazily) containing the contents of the the
file specified by the ReadFile request.
o Failure (SearchError name) occurs if file "name" cannot be
accessed.
o Failure (ReadError name) occurs if some other error occurs
whilst opening the file "name".
o WriteFile name string: Write the given "string" to the file
"name". If the file does not already exist, it is created before
attempting to write the value to file. If the file already exists
then it will be truncated to zero length before the write begins.
No response is obtained until the string argument has been fully
evaluated and its contents written to file. Possible responses
are:
o Success if the write to file was completed successfully.
o Failure (WriteError msg) if an error was detected whilst
trying to perform the output. If the problem occurred whilst
attempting to open the specified file, then "msg" contains
the filename, otherwise it contains a printable
representation of the evaluation error which occurred.
o AppendFile name string: Similar to the WriteFile request except
that the value of the given "string" is appended onto the file
"name" if that file already exists. The responses that may be
obtained from this request are the same as those for WriteFile.
o ReadChan name: Read from the input stream "name". Note that
it is an error to attempt to read from the same channel more than
once in the same program. Possible responses are:
o Str contents if the request is successful, where "contents"
is a string (evaluated lazily) containing the list of
characters entered on the input stream.
o Failure (SearchError name) if the named channel cannot be
found. The only input channel known to Gofer is the standard
input channel "stdin". For convenience, the standard prelude
defines the variable stdin bound to this string.
o Failure (ReadError name) if a ReadChan request for the named
channel has already been given by a previous request.
o AppendChan name string: Output "string" on channel "name". No
response is obtained until the string has been fully evaluated and
written to the named channel. Possible responses are:
o Success if the append to channel was completed successfully.
o Failure (SearchError name) if the named channel cannot be
50
Introduction to Gofer 12.1 Basic description
found. The only output channels known to Gofer are "stdout",
"stderr" and "stdecho" (which is actually just another name
for "stdout" in Gofer). For convenience, the standard
prelude defines variables stdout, stderr and stdecho bound to
the corresponding string values.
o Failure (WriteError msg) if an error is detected whilst
trying to perform the output. The string "msg" contains a
printable representation of the evaluation error which
occurred.
o Echo status: Set the echo status on the standard input channel
stdin to the given boolean value. If the echo status is True,
then user input will be echoed onto the screen as they are typed
and the usual line editing facilities (such a backspace or delete)
provided by the host system can be used to edit the input lines as
they are entered. If the echo status is False, then individual
characters may be read from the standard input channel without any
echo or line editing features.
Note that at most one Echo request can be used in a program, and
must precede any ReadChan request for stdin. If not set by an
explicit Echo request, the echo status defaults to True. Possible
responses are:
o Success if the request was completed successfully.
o Failure (OtherError msg) if the request could not be
completed either because a readChannel request for stdin has
already been processed, or because a previous Echo request
has already been given. The corresponding values of "msg"
are "stdin already in use" and "repeated Echo request"
respectively.
A simple example of a program using these facilities to output a short
message on the standard output stream is:
helloWorld :: Dialogue
helloWorld resps = [AppendChan stdout "hello, world"]
Any expression entered into Gofer of type "Dialogue" will be treated as
a Gofer program using I/O and will be executed accordingly:
? helloWorld
hello, world
(1 reduction, 28 cells)
?
Notice that without the explicit type declaration, the type that would
be inferred for for helloWorld would be a -> [Request], and hence
helloWorld would not be executed as a Dialogue program. This point can
be illustrated using lambda expressions:
? \resps -> [AppendChan stdout "hello, world"]
v128
(1 reduction, 7 cells)
51
Introduction to Gofer 12.1 Basic description
? (\resps -> [AppendChan stdout "hello, world"]) :: Dialogue
hello, world
(1 reduction, 28 cells)
?
In many cases the structure of an expression is enough to fully
determine its type as Dialogue (or equivalently as [Response] ->
[Request]), in which case no explicit types are required to ensure that
the expression is treated as a Gofer program using I/O:
? \~[Success] -> [AppendChan stdout "hello, world"]
hello, world
(1 reduction, 29 cells)
?
Note the use of the irrefutable pattern ~[Success] for the lambda
expression in the last example; without this, the usual rules of
pattern matching as described in section 9 would force Gofer to try and
match the pattern [Success] against the list of responses, before the
corresponding request had been produced:
? \ [Success] -> [AppendChan stdout "hello, world"]
Aborting Dialogue:
{error "Attempt to read response before request complete"}
(50 reductions, 229 cells)
?
The next example takes a single string a parameter and displays the
contents of the corresponding file:
showFile :: String -> Dialogue
showFile name ~(read:_) = [ReadFile name, AppendChan stdout result]
where result = case read of Str contents -> contents
Failure _ -> "Can't open " ++ name
With a few modifications, we can implement a similar program which
prompts for, and reads, a filename from the standard input and then
reads and displays the contents of that file as before. This program
is based on a similar example in the Haskell report [5]:
main ~(Success : ~(Str userInput : ~(r3 : _)))
= [ AppendChan stdout "Please type a filename: ",
ReadChan stdin,
ReadFile name,
AppendChan stdout (case r3 of Str contents -> contents
Failure _ -> "Can't open "
++ name)
] where (name : _) = lines userInput
52
Introduction to Gofer 12.2 Continuation style I/O
12.2 Continuation style I/O
---------------------------
As an alternative to the `stream-based' approach to programs using the
I/O facilities in Gofer, the standard prelude defines a family of
functions which enables such programs to be written in a `continuation'
style. The basic idea is to define a function corresponding to each
different kind of request, whose parameters include the values required
to make the request together with two continuations. The continuations
are functions describing "what to do next", one of which is used if the
request is successful, the other if the request fails.
As an example, the ReadFile request is represented by the function
"readFile" whose definition is equivalent to:
readFile name fail succ ~(r:rs) = ReadFile name : rest rs
where rest = case r of Str s -> succ s
Failure ioerror -> fail ioerror
The first thing to happen when a dialogue expression of the form
"readFile name fail succ" is evaluated is that the corresponding
request "ReadFile name" is added to the list of I/O requests. A new
dialogue value "rest" is chosen, depending on the response to the
ReadFile request, and the program continues by passing the remaining
part of the response list to "rest". The functions "succ" and "fail"
(called the success and failure continuations respectively) describe
the way in which the new dialogue "rest" is obtained.
The following example (edited a little to fit within the margins of this
document) shows how the readFile function described above can be used to
print the contents of a file called "test" on the display:
? readFile "test" (\ioerror resps -> [])
(\s resps->[AppendChan stdout s])
This is a test message
(4 reductions, 52 cells)
?
The success continuation "(\s resps->[AppendChan stdout s])" used here
receives the contents of the file "test" in the the parameter "s" and
uses an AppendChan request to output that string on the display. As
this example shows, the stream based approach of the previous section
can be combined with the continuation based style of I/O without any
difficulty. The failure continuation "(\ioerror resps -> [])" ignores
the error condition "ioerror" which caused the request to fail and
gives a dialogue which terminates immediately without any action. For
example, assuming that the file "Test" cannot be found:
? readFile "Test" (\ioerror resps -> [])
(\s resps->[AppendChan stdout s])
(4 reductions, 24 cells)
?
In practice, it is usually a good idea to produce some kind of
diagnostic message when an error occurs:
53
Introduction to Gofer 12.2 Continuation style I/O
? readFile "Test"
(\ioerror resps -> [AppendChan stdout (show' ioerror)])
(\s resps -> [AppendChan stdout s])
SearchError "Test"
(11 reductions, 59 cells)
?
In each of the examples above, the failure continuation has type
"FailCont" as defined by the following type synonym in the standard
prelude:
type FailCont = IOError -> Dialogue
Similarly, the success continuation, which takes a string representing
an input string and produces a new Dialogue has type "StrCont":
type StrCont = String -> Dialogue
A third kind of continuation is needed for those requests which return
a response of the form "Success" if successful (e.g. output
requests). In this case the continuation is simply another dialogue:
type SuccCont = Dialogue
The following list gives the type of each of the six functions
corresponding to the six different kinds of I/O request described in
the previous section. Full definitions for each of these functions are
given in appendix B:
readFile :: String -> FailCont -> StrCont -> Dialogue
writeFile :: String -> String -> FailCont -> SuccCont -> Dialogue
appendFile :: String -> String -> FailCont -> SuccCont -> Dialogue
readChan :: String -> FailCont -> StrCont -> Dialogue
appendChan :: String -> String -> FailCont -> SuccCont -> Dialogue
echo :: Bool -> FailCont -> SuccCont -> Dialogue
As an illustration of the use of these functions, we show how each of
the example programs from the previous section can be rewritten using
the continuation based style of I/O, starting with the program
"helloWorld":
helloWorld :: Dialogue
helloWorld = appendChan stdout "hello, world" abort done
In this case, the explicit type declaration is not actually required
since the type of the expression is completely determined by the type
of "appendChan". The failure continuation "abort" is equivalent to the
function "(\ioerror resps -> [])" described above and terminates the
program if an error occurs without any further action. In a similar
way, "done" is the trivial dialogue which terminates immediately
without any action. Both of these values are defined in the standard
prelude:
done :: Dialogue
done resps = []
54
Introduction to Gofer 12.2 Continuation style I/O
abort :: FailCont
abort ioerror = done
Using the same approach, the "showFile" and "main" programs from the
previous section are written as:
showFile :: String -> Dialogue
showFile name
= readFile name (\ioerror -> appendChan stdout
("Can't open " ++ name) abort done)
(\contents-> appendChan stdout contents abort done)
main :: Dialogue
main = appendChan stdout "Please type a filename: " abort
(readChan stdin abort
(\userInput -> let (name : _) = lines userInput in
readFile name
(\ioerror -> appendChan stdout ("Can't open " ++ name)
abort done)
(\contents -> appendChan stdout contents abort done)))
12.3 Interactive programs
-------------------------
One of the principal motivations for including facilities for I/O in
Gofer programs was to provide a way of using interactive programs as
described in [1]. An interactive program is represented by a function
of type String -> String mapping an input string of characters entered
at the keyboard into an output string to be displayed on the screen.
There are two functions defined in the standard prelude which can be
used to `execute' functions of this kind as interactive programs:
o "interact f" executes f::String->String as an interactive program
with echo on. This means that characters are read from the
keyboard a line at a time. The usual editing characters such as
backspace can be used to correct mistakes which are noticed before
the return key is pressed at the end of each line. The input
stream can be terminated by typing an end of file character at the
beginning of a line:
? interact (map toUpper)
This text was entered using the interact function
THIS TEXT WAS ENTERED USING THE INTERACT FUNCTION
^Z
(874 reductions, 1037 cells)
?
o "run f" behaves like "interact f" except that echo is turned off.
In this case, the only way of terminating the input stream without
reaching the end of the string produced by "f" is to use the
interrupt key:
? run (map toUpper)
ALTHOUGH THIS IS ENTERED IN LOWER CASE, IT STILL
APPEARS IN UPPER CASE !
55
Introduction to Gofer 12.3 Interactive programs
{Interrupted!}
(1227 reductions, 1463 cells)
?
[ASIDE: of these two functions, only "interact" is also included in the
standard prelude for Haskell, although "run" may also be added to a
Haskell system using the definition below.]
The definitions of "interact" and "run" provide further examples of
Gofer programs using simple I/O facilities:
interact :: (String -> String) -> Dialogue
interact f = readChan stdin abort
(\s -> appendChan stdout (f s) abort done)
run :: (String -> String) -> Dialogue
run f = echo False abort (interact f)
[EXERCISE for the interested reader: construct alternative definitions
for these functions using the stream based approach from section 12.1.]
56